package com.jeremyaburns.oddio.tracker 
{
	import com.jeremyaburns.Vector2D;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	
	internal class OTNoteboard extends Sprite
	{
		// Events
		static public const PLAY_NOTE   :String = "otplaynote";
		static public const MOVE_CAMERA :String = "otmovecamera";
		
		
		// Holds the vertical and horizontal stripes which denote where notes go.
		private var verticalStripes   :Sprite;
		private var horizontalStripes :Sprite;
		
		private var playhead :Sprite;
		
		// This holds all the OTNote sprites so they can be moved around easily.
		private var noteContainer :Sprite;
		private var notes :Vector.<OTNote>;
		
		// Camera state variables.
		private var camera       :Vector2D;
		private var mouseDown    :Boolean;
		private var mouseDownPos :Vector2D;
		private var mouseDownCam :Vector2D;
		private var movingCamera :Boolean;
		
		
		public function get playheadIndex() :int { return int(((playhead.x)-OTStyle.noteWidth/2)/OTStyle.noteWidth); }
		public function set playheadIndex( rhs:int ) :void{
			playhead.x = OTStyle.noteWidth/2 + OTStyle.noteWidth*rhs;
		}
		
		
		// Manage viewport height.  This determines how far downwards you're allowed to scroll.
		private var mViewportHeight :Number;

		
		public function getNotes() :Vector.<OTNote>
		{
			return notes;
		}

		
		
		public function OTNoteboard() 
		{
			var i:int;
			
			// Create storage for the notes.
			noteContainer = new Sprite();
			notes = new Vector.<OTNote>();
			
			// Create a transparent rect to put in front of everything and capture mouse events.
			var mouseBox :Sprite = new Sprite();
			mouseBox.graphics.lineStyle();
			mouseBox.graphics.beginFill( 0, 0 );
			mouseBox.graphics.drawRect( 0, 0, 2000, 2000 );
			mouseBox.graphics.endFill();
			
			playhead = new Sprite();
			playhead.graphics.lineStyle( 2, 0xCCCCCC );
			playhead.graphics.moveTo( 0, 0 );
			playhead.graphics.lineTo( 0, 2000 );
			playhead.x = OTStyle.noteWidth/2;

			// Draw the vertical stripes out to the right.
			verticalStripes = new Sprite();
			for( i = 0 ; i < 2000 ; i += OTStyle.noteWidth ) {
				if( (i/OTStyle.noteWidth) % 16 == 0 ) {
					verticalStripes.graphics.lineStyle( 2, 0x333333 );
				} else if( (i/OTStyle.noteWidth) % 4 == 0 ) {
					verticalStripes.graphics.lineStyle( 2, 0x1A1A1A );
				} else {
					verticalStripes.graphics.lineStyle( 1, 0x111111 );
				}
				verticalStripes.graphics.moveTo( i,    0 );
				verticalStripes.graphics.lineTo( i, 2000 );
			}
			
			// Draw the horizontal stripes downwards.
			horizontalStripes = new Sprite();
			for( i = 0 ; i < 2000 ; i += OTStyle.noteHeight ) {
				if( (i/OTStyle.noteHeight) % 12 == 0 ) {
					horizontalStripes.graphics.lineStyle( 2, 0x333333 );
				} else {
					horizontalStripes.graphics.lineStyle( 1, 0x111111 );
				}
				horizontalStripes.graphics.moveTo(    0, i );
				horizontalStripes.graphics.lineTo( 2000, i );
			}
			
			// Populate the display list.
			addChild( horizontalStripes );
			addChild( verticalStripes   );
			addChild( noteContainer     );
			addChild( mouseBox          );
			noteContainer.addChild( playhead );
			
			// Initialize the camera state variables.
			camera       = new Vector2D();
			mouseDown    = false;
			mouseDownPos = new Vector2D();
			mouseDownCam = new Vector2D();
			movingCamera = false;
			
			// Set default viewport height to zero to allow scrolling all the way down in any container.
			mViewportHeight = 480;
			
			// Begin monitoring mouse events for moving the camera and adding notes.
			this.addEventListener( MouseEvent.MOUSE_DOWN,  this_mouseDown  );
			this.addEventListener( MouseEvent.MOUSE_MOVE,  this_mouseMove  );
			this.addEventListener( MouseEvent.MOUSE_OUT,   this_mouseUpOut );
			this.addEventListener( MouseEvent.MOUSE_UP,    this_mouseUpOut );
			this.addEventListener( MouseEvent.MOUSE_WHEEL, this_mouseWheel );			
		}
		
		
		
		// This will get a note at a specific time and pitch.  If pitch is -1, any pitch is fine.
		private function getNote( timeIndex:int, pitch:int=-1 ) :OTNote
		{
			for( var i:int = 0 ; i < notes.length ; ++i ) {
				if( notes[i].timeIndex == timeIndex && (pitch==-1 || notes[i].pitch == pitch) ) {
					return notes[i];
				}
			}
			return null;
		}

		
		public function resize( w:Number, h:Number ) :void
		{
			mViewportHeight = h;
			if( camera.y > 96*OTStyle.noteHeight - mViewportHeight ) {
				camera.y = 96*OTStyle.noteHeight - mViewportHeight;
				updateCamera();
			}
		}

		
		// On mouse down, we either want to start moving the camera, or create a note.
		private function this_mouseDown( e:MouseEvent ) :void
		{
			mouseDown = true;
			mouseDownPos.setTo( e.localX, e.localY );
			
			// If CTRL is being held, then we're moving the camera.
			if( e.ctrlKey ) {
				mouseDownCam.copyFrom( camera );
				movingCamera = true;
			}
			// If neither SHIFT or CTRL are being held, create or move a note.
			else if( !e.shiftKey )
			{	
				var timeIndex :int =    int( (e.localX + camera.x) / OTStyle.noteWidth  );
				var pitch     :int = 95-int( (e.localY + camera.y) / OTStyle.noteHeight );
				var note:OTNote = getNote( timeIndex );
				if( note ) {
					if( note.pitch == pitch ) return;
					note.pitch = pitch;
				} else {
					note = new OTNote( timeIndex, pitch );
					noteContainer.addChild( note );
					notes.push( note );
				}
				dispatchEvent( new OTEvent( PLAY_NOTE, note.pitch ) );
				return;
			}
			
			// If either CTRL or SHIFT are being held, pass the event to mouseMove so we can move the camera or delete notes.
			this_mouseMove( e );
		}
		
		
		// When the mouse is dragged across the noteboard, we're either deleting notes or moving the camera.
		private function this_mouseMove( e:MouseEvent ) :void
		{
			// Don't do anything unless the mouse is being held.
			if( !mouseDown ) return;
			
			// If we're moving the camera, then adjust it's position within bound and update the display.
			if( movingCamera )
			{
				camera.x = mouseDownCam.x + mouseDownPos.x - e.localX;
				camera.y = mouseDownCam.y + mouseDownPos.y - e.localY;
				if( camera.x < 0 ) camera.x = 0;
				if( camera.y < 0 ) camera.y = 0;
				if( camera.y > 96*OTStyle.noteHeight - mViewportHeight ) {
					camera.y = 96*OTStyle.noteHeight - mViewportHeight;
				}
				updateCamera();
			}
			
			// If we're deleting notes, figure out what time/pitch the click was at and try to kill a note there.
			else if( e.shiftKey )
			{
				var timeIndex :int =    int( (e.localX + camera.x) / OTStyle.noteWidth  );
				var pitch     :int = 95-int( (e.localY + camera.y) / OTStyle.noteHeight );
				var note:OTNote = getNote( timeIndex, pitch );
				if( note ) {
					noteContainer.removeChild( note );
					notes.splice( notes.indexOf( note ), 1 );
				}
			}
		}
		
		private function this_mouseWheel( e:MouseEvent ) :void
		{
			var timeIndex :int =    int( (e.localX + camera.x) / OTStyle.noteWidth  );
			var pitch     :int = 95-int( (e.localY + camera.y) / OTStyle.noteHeight );
			var note :OTNote = getNote( timeIndex, pitch );
			if( note == null ) return;
			
			if( e.delta > 0 ) {
				note.duration++;
			} else {
				note.duration--;
			}
		}
		
		// When the mouse button is released or the cursor leaves the noteboard, stop processing mouse input.
		private function this_mouseUpOut( e:MouseEvent ) :void
		{
			mouseDown = false;
			movingCamera = false;
		}
		
		// Move the stripes and noteboard according to the camera's position.
		// This is called every time the camera's position changes.
		private function updateCamera() :void
		{
			var intPart:int, decPart:Number;
			
			intPart = int(camera.x);
			decPart = Math.ceil(camera.x) - camera.x;
			verticalStripes.x = -((intPart % (OTStyle.noteWidth*16)) + decPart);
			
			intPart = int(camera.y);
			decPart = Math.ceil(camera.y) - camera.y;
			horizontalStripes.y = -((intPart % (OTStyle.noteHeight*12)) + decPart);
			
			noteContainer.x = -camera.x;
			noteContainer.y = -camera.y;
			
			dispatchEvent( new OTEvent( MOVE_CAMERA, camera.cloneVector() ) );
		}
	}
}